Binary subarrays with sum¶
Time: O(N); Space: O(1); medium
In an array A of 0s and 1s, how many non-empty subarrays have sum S?
Example 1:
Input: A = [1,0,1,0,1], S = 2
Output: 4
Explanation:
The 4 subarrays are bolded below:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
Example 2:
Input: A = [0,0,0,0,0,0,1,0,0,0], S = 0
Output: 27
Explanation:
And 27 subarrays for S.
Constraints:
0 < len(A) <= 30000
0 <= S <= len(A)
A[i] is either 0 or 1.
1. Three Pointer¶
Intuition
For each j, let’s try to count the number of i’s that have the subarray [i, j] equal to S.
It is easy to see these i’s form an interval [i_lo, i_hi], and each of i_lo, i_hi are increasing with respect to j. So we can use a “two pointer” style approach.
Algorithm
For each j (in increasing order), let’s maintain 4 variables:
sum_lo : the sum of subarray [i_lo, j]
sum_hi : the sum of subarray [i_hi, j]
i_lo : the smallest i so that sum_lo <= S
i_hi : the largest i so that sum_hi <= S
Then, (provided that sum_lo == S), the number of subarrays ending in j is i_hi - i_lo + 1.
As an example, with A = [1,0,0,1,0,1] and S = 2, when j = 5, we want i_lo = 1 and i_hi = 3.
[16]:
class Solution1(object):
"""
Time: O(N)
Space: O(1)
"""
def numSubarraysWithSum(self, A, S):
"""
:type A: List[int]
:type S: int
:rtype: int
"""
result = 0
i_lo = i_hi = 0
sum_lo = sum_hi = 0
for j, x in enumerate(A):
# Maintain i_lo, sum_lo: While the sum is too big, i_lo += 1
sum_lo += x
while i_lo < j and sum_lo > S:
sum_lo -= A[i_lo]
i_lo += 1
# Maintain i_hi, sum_hi: While the sum is too big, or equal and we can move, i_hi += 1
sum_hi += x
while i_hi < j and (sum_hi > S or sum_hi == S and not A[i_hi]):
sum_hi -= A[i_hi]
i_hi += 1
if sum_lo == S:
result += i_hi - i_lo + 1
return result
[17]:
s = Solution1()
A = [1,0,1,0,1]
S = 2
assert s.numSubarraysWithSum(A, S) == 4
A = [0,0,0,0,0,0,1,0,0,0]
S = 0
assert s.numSubarraysWithSum(A, S) == 27
2. Three Pointer (variant)¶
[18]:
class Solution2(object):
"""
Time: O(N)
Space: O(1)
"""
def numSubarraysWithSum(self, A, S):
"""
:type A: List[int]
:type S: int
:rtype: int
"""
result = 0
left, right, sum_left, sum_right = 0, 0, 0, 0
for i, a in enumerate(A):
sum_left += a
while left < i and sum_left > S:
sum_left -= A[left]
left += 1
sum_right += a
while right < i and (sum_right > S or (sum_right == S and not A[right])):
sum_right -= A[right]
right += 1
if sum_left == S:
result += right-left+1
return result
[19]:
s = Solution2()
A = [1,0,1,0,1]
S = 2
assert s.numSubarraysWithSum(A, S) == 4
A = [0,0,0,0,0,0,1,0,0,0]
S = 0
assert s.numSubarraysWithSum(A, S) == 27
3. Index of Ones [O(N), O(N)]¶
Intuition
Say we number the 1s in A: (x_1, x_2, …, x_n) with A[x_i] = 1.
Then, if we have a subarray of sum S, it has to use the ones x_i, x_{i+1}, …, x_{i+S-1}.
For each i, we can count the number of such subarrays individually.
Algorithm
In general, the number of such subarrays (for i) is (x_i - x_{i-1}) * (x_{i+S} - x_{i+S-1}).
For example, if S = 2, then in A = [1,0,1,0,1,0,0,1], let’s count the number of subarrays [i, j] that use the middle two 1s.
There are 2 choices for the i (i = 1, 2) and 3 choices for the j (j = 4, 5, 6).
The corner cases are when S = 0, i = 1, or i+S = n+1. We can handle these gracefully.
[20]:
class Solution3(object):
"""
Time: O(N)
Space: O(N)
"""
def numSubarraysWithSum(self, A, S):
"""
:type A: List[int]
:type S: int
:rtype: int
"""
indexes = [-1] + [ix for ix, v in enumerate(A) if v] + [len(A)]
ans = 0
if S == 0:
for i in range(len(indexes) - 1):
# w: number of zeros between two consecutive ones
w = indexes[i+1] - indexes[i] - 1
ans += w * (w+1) / 2
return ans
for i in range(1, len(indexes) - S):
j = i + S - 1
left = indexes[i] - indexes[i-1]
right = indexes[j+1] - indexes[j]
ans += left * right
return ans
[21]:
s = Solution3()
A = [1,0,1,0,1]
S = 2
assert s.numSubarraysWithSum(A, S) == 4
A = [0,0,0,0,0,0,1,0,0,0]
S = 0
assert s.numSubarraysWithSum(A, S) == 27
4. Prefix Sums [O(N), O(N)]¶
Intuition
Let P[i] = A[0] + A[1] + … + A[i-1]. Then P[j+1] - P[i] = A[i] + A[i+1] + … + A[j], the sum of the subarray [i, j].
Hence, we are looking for the number of i < j with P[j] - P[i] = S.
Algorithm
For each j, let’s count the number of i with P[j] = P[i] + S.
This is analogous to counting the number of subarrays ending in j with sum S.
It comes down to counting how many P[i] + S we’ve seen before.
We can keep this count on the side to help us find the final answer.
[22]:
import collections
class Solution4(object):
"""
Time: O(N)
Space: O(N)
"""
def numSubarraysWithSum(self, A, S):
"""
:type A: List[int]
:type S: int
:rtype: int
"""
P = [0]
for x in A:
P.append(P[-1] + x)
count = collections.Counter()
ans = 0
for x in P:
ans += count[x]
count[x + S] += 1
return ans
[23]:
s = Solution4()
A = [1,0,1,0,1]
S = 2
assert s.numSubarraysWithSum(A, S) == 4
A = [0,0,0,0,0,0,1,0,0,0]
S = 0
assert s.numSubarraysWithSum(A, S) == 27